#include "DoubleStackAllocator.h"
#include "Assertions.h"

#include <algorithm>

namespace CEngine
{

DoubleStackAllocator::DoubleStackAllocator(U64 stackSize)
    :   freeBytes(stackSize),
        totalBytes(stackSize)
{
    stackBottom = stackPointer = new U8[stackSize];
    stackTop = stackPointer + stackSize;
}

DoubleStackAllocator::DoubleStackAllocator(const DoubleStackAllocator& stack, U64 stackSize)
    :   freeBytes(stackSize),
        totalBytes(stackSize)
{
    ASSERT(stackSize >= stack.totalBytes);

    stackPointer = new U8[stackSize];

    std::copy((U8*)stack.stackPointer, (U8*)stack.stackPointer + stack.totalBytes, (U8*)stackPointer);
    stackBottom = (U8*)stackPointer + ((U8*)stack.stackBottom - (U8*)stack.stackPointer);
    stackTop = ((U8*)stackPointer + stackSize) - ((U8*)stack.stackPointer - (U8*)stack.stackTop);
}

DoubleStackAllocator::~DoubleStackAllocator(void)
{
    delete[] (U8*)stackPointer;
    stackPointer = NULL;
    stackTop = NULL;
    stackBottom = NULL;
    freeBytes = 0;
    totalBytes = 0;
}

void*
DoubleStackAllocator::alloc(U64 bytes, StackSide side) {
    if (bytes > freeBytes)
        return NULL;

    void* temp;

    if (side == BOTTOM) {
        temp = stackBottom;
        stackBottom += bytes;
    }
    else {
        stackTop -= bytes;
        temp = stackTop;
    }

    freeBytes -= bytes;

    return temp;
}

void*
DoubleStackAllocator::alignedAlloc(U64 bytes, U64 alignment, StackSide side) {
    ///use normal alloc if alignment == 1
    ASSERT(alignment > 1);

    U64 expandedBytes = bytes + alignment;

    Marker marker = getMarker(side);

    U64 rawAddress = (U64)alloc(expandedBytes, side);
    if (rawAddress == NULL)
        return NULL;

    U64 mask = alignment - 1;
    U64 misalignment = (rawAddress & mask);
    U64 adjustment = alignment - misalignment;
    U64 alignedAddress = rawAddress + adjustment;
    if (side == BOTTOM)
        freeToMarker((marker + expandedBytes) - adjustment, side);
    else
        freeToMarker((marker - expandedBytes) + adjustment, side);

    return (void*)alignedAddress;
}

void
DoubleStackAllocator::freeToMarker(Marker marker, StackSide side) {
    if ((void*)marker == NULL) {
        return;
    }
    if (side == BOTTOM) {
        freeBytes += (U64)stackBottom - marker;
        stackBottom = (void*)marker;
    }
    else {
        freeBytes += marker - (U64)stackTop;
        stackTop = (void*)marker;
    }
}

void
DoubleStackAllocator::clear(void) {
    stackBottom = stackPointer;
    stackTop = stackPointer + totalBytes;
    freeBytes = totalBytes;
}

}
